package com.wnxy.sddfp.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sddfp.common.result.Result;
import com.wnxy.sddfp.common.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.util.JwtTemplate;
import com.wnxy.sddfp.gateway.config.IgnoreUrisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.function.Predicate;

/**
 * gateway有两种filter，
 * 1、GlobalFilter：全局过滤器，对所有路由生效。通过实现GlobalFilter接口创建
 * 2、GatewayFilter：网关过滤器，也可以说是局部过滤器，只对配置了此过滤器的路由生效。
 *   通过GatewayFilterFactory创建。
 *
 * 过滤器执行顺序？
 *   源码：FilterWebHandler的handle方法，
 *   先拿到配置文件里的gatewayFilter，在拿GlobalFilter，然后排序得到最终的执行顺序。
 *   最终都是通过Order值进行排序执行，Order值越小越先执行？
 *
 *   1、两个GlobalFilter类型的过滤器Order值相同时，根据文件名字母排序，文件名靠前的优先更高。
 *     原因是包扫描时是按照文件的顺序扫描的，然后封装到List集合的，
 *     通过Order值排序时如果Order值相同，文件名在前名的依然会排在前面。
 *   2、GlobalFilter类型和GatewayFilter类型的过滤器Order值相同时，GlobalFilter类型优先更高。
 *     原因是这两种过滤器最终会合并到一个过滤器集合中形成过滤器调用链，源码是通过list.addAll();
 *     方法将GatewayFilter类型的过滤器加到了GlobalFilter过滤器集合中，
 *     addAll()是末尾添加方式，所以Order值相同时GatewayFilter类型的过滤器会排在后面。
 *
 * Order值生成规则？
 *   1、GlobalFilter类型过滤器，通过实现Ordered接口的getOrder()方法设置。
 *   2、GatewayFilter类型过滤器，无法手动设置Order值，通过配置文件中配置的过滤器顺序自动生成，
 *   固定从1开始封装，例如配置了三个过滤器，则按照从上往下顺序Order值依次为1、2、3。
 */
@Configuration
public class AuthFilter implements GlobalFilter, Ordered {
    @Autowired
    private IgnoreUrisConfig ignoreUrisConfig;
    @Autowired
    private JwtTemplate jwtTemplate;

    private static final String AUTHORIZATION = "Authorization";
    private static final String TOKEN_BEARER = "Bearer ";
    /**
     * 网关统一鉴权业务处理
     * 1、获取当前网关请求的路径
     * 2、放行一些资源：登录、注册请求，不需要进行鉴权; 除了放行的请求，其他请求都需要认证
     * 3、获取请求头；前端传入的请求头（Authorization "Bearer token"）
     * 4、校验token、解析token数据
     * 5、在网关中重写请求头，存放解析的token数据；这样，在微服务中可以直接从请求头获取token数据
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //1、获取当前网关请求的路径
        String path = request.getURI().getPath();
        //2、放行一些资源：登录、注册请求，不需要进行鉴权; 除了放行的请求，其他请求都需要认证
        List<String> uris = ignoreUrisConfig.getUris();
        PathMatcher pathMatcher = new AntPathMatcher();
        if (uris.stream().anyMatch(uri -> pathMatcher.match(uri, path))){
            return chain.filter(exchange);
        }

        //3、获取请求头；前端传入的请求头（Authorization "Bearer token"）
        String authToken = request.getHeaders().getFirst(AUTHORIZATION);
        if (StringUtils.isEmpty(authToken)) {
            return error(response);
        }
        authToken = authToken.replace(TOKEN_BEARER,"");

        //4、校验token、解析token数据
        boolean verify = jwtTemplate.verify(authToken);
        if (!verify) {
            // token 校验失败，返回错误
            return error(response);
        }
        // 解析token，获取token数据
        String id = (String) jwtTemplate.parseToken(authToken, "id");
        String phone = (String) jwtTemplate.parseToken(authToken, "phone");

        //5、在网关中重写请求头，存放解析的token数据；这样，在微服务中可以直接从请求头获取token数据
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header("id",id);
        mutate.header("phone",phone);

        return chain.filter(exchange);
    }

    /**
     * 返回错误的方法
     * @param response
     * @return
     */
    private Mono<Void> error(ServerHttpResponse response) {
        try {
            Result result = Result.fail(BusinessCode.NO_AUTH);
            byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
            DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(dataBuffer));
        } catch (Exception e) {
            e.printStackTrace();
            return response.writeWith(Mono.error(e));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
